 /*******************************************************************************
  * Copyright (c) 2005, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.keys;

 import java.io.IOException ;
 import java.net.URL ;
 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Collection ;
 import java.util.Collections ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Locale ;
 import java.util.Set ;

 import org.eclipse.core.commands.Category;
 import org.eclipse.core.commands.Command;
 import org.eclipse.core.commands.CommandManager;
 import org.eclipse.core.commands.ParameterizedCommand;
 import org.eclipse.core.commands.common.NamedHandleObject;
 import org.eclipse.core.commands.common.NamedHandleObjectComparator;
 import org.eclipse.core.commands.common.NotDefinedException;
 import org.eclipse.core.commands.contexts.Context;
 import org.eclipse.core.commands.contexts.ContextManager;
 import org.eclipse.core.commands.util.Tracing;
 import org.eclipse.core.databinding.observable.Diffs;
 import org.eclipse.core.databinding.observable.IStaleListener;
 import org.eclipse.core.databinding.observable.StaleEvent;
 import org.eclipse.core.databinding.observable.set.IObservableSet;
 import org.eclipse.core.databinding.observable.set.ISetChangeListener;
 import org.eclipse.core.databinding.observable.set.ObservableSet;
 import org.eclipse.core.databinding.observable.set.SetChangeEvent;
 import org.eclipse.core.databinding.observable.set.UnionSet;
 import org.eclipse.core.databinding.observable.set.WritableSet;
 import org.eclipse.core.databinding.observable.value.IObservableValue;
 import org.eclipse.core.databinding.observable.value.IValueChangeListener;
 import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.bindings.Binding;
 import org.eclipse.jface.bindings.BindingManager;
 import org.eclipse.jface.bindings.Scheme;
 import org.eclipse.jface.bindings.keys.KeyBinding;
 import org.eclipse.jface.bindings.keys.KeySequence;
 import org.eclipse.jface.bindings.keys.KeySequenceText;
 import org.eclipse.jface.bindings.keys.KeyStroke;
 import org.eclipse.jface.contexts.IContextIds;
 import org.eclipse.jface.databinding.swt.SWTObservables;
 import org.eclipse.jface.databinding.viewers.ViewersObservables;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.internal.databinding.provisional.swt.ControlUpdater;
 import org.eclipse.jface.preference.PreferencePage;
 import org.eclipse.jface.resource.DeviceResourceException;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.resource.LocalResourceManager;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jface.viewers.AbstractListViewer;
 import org.eclipse.jface.viewers.ArrayContentProvider;
 import org.eclipse.jface.viewers.ComboViewer;
 import org.eclipse.jface.viewers.IBaseLabelProvider;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.ITableLabelProvider;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.NamedHandleObjectLabelProvider;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.TreeViewer;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerComparator;
 import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.BusyIndicator;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.events.FocusEvent;
 import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.MenuItem;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.swt.widgets.Tree;
 import org.eclipse.swt.widgets.TreeColumn;
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPreferencePage;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.commands.ICommandService;
 import org.eclipse.ui.contexts.IContextService;
 import org.eclipse.ui.dialogs.FilteredTree;
 import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
 import org.eclipse.ui.internal.WorkbenchPlugin;
 import org.eclipse.ui.internal.commands.ICommandImageService;
 import org.eclipse.ui.internal.misc.Policy;
 import org.eclipse.ui.internal.misc.StatusUtil;
 import org.eclipse.ui.internal.util.BundleUtility;
 import org.eclipse.ui.internal.util.Util;
 import org.eclipse.ui.keys.IBindingService;
 import org.eclipse.ui.statushandlers.StatusManager;

 /**
  * <p>
  * A preference page that is capable of displaying and editing the bindings
  * between commands and user input events. These are typically things like
  * keyboard shortcuts.
  * </p>
  * <p>
  * This preference page has four general types of methods. Create methods are
  * called when the page is first made visible. They are responsible for creating
  * all of the widgets, and laying them out within the preference page. Fill
  * methods populate the contents of the widgets that contain collections of data
  * from which items can be selected. The select methods respond to selection
  * events from the user, such as a button press or a table selection. The update
  * methods update the contents of various widgets based on the current state of
  * the user interface. For example, the command name label will always try to
  * match the current select in the binding table.
  * </p>
  *
  * @since 3.2
  */
 public final class NewKeysPreferencePage extends PreferencePage implements
         IWorkbenchPreferencePage {

     private static boolean DEBUG = Policy.DEBUG_KEY_BINDINGS;

     private static final String TRACING_COMPONENT = "NewKeysPref"; //$NON-NLS-1$

     /**
      * @since 3.3
      *
      */
     private final class ResortColumn extends SelectionAdapter {
         private final BindingComparator comparator;
         private final TreeColumn treeColumn;
         private final Tree tree;
         private final int column;

         /**
          * @param comparator
          * @param commandNameColumn
          * @param tree
          */
         private ResortColumn(BindingComparator comparator,
                 TreeColumn treeColumn, Tree tree, int column) {
             this.comparator = comparator;
             this.treeColumn = treeColumn;
             this.tree = tree;
             this.column = column;
         }

         public void widgetSelected(SelectionEvent e) {
             if (comparator.getSortColumn() == column) {
                 comparator.setAscending(!comparator.isAscending());
             }
             tree.setSortColumn(treeColumn);
             comparator.setSortColumn(column);
             tree.setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
             try {
                 filteredTree.getViewer().getTree().setRedraw(false);
                 filteredTree.getViewer().refresh();
             } finally {
                 filteredTree.getViewer().getTree().setRedraw(true);
             }
         }
     }

     private class ObservableSetContentProvider implements ITreeContentProvider {

         private class KnownElementsSet extends ObservableSet {

             KnownElementsSet(Set wrappedSet) {
                 super(SWTObservables.getRealm(Display.getDefault()),
                         wrappedSet, Object .class);
             }

             void doFireDiff(Set added, Set removed) {
                 fireSetChange(Diffs.createSetDiff(added, removed));
             }

             void doFireStale(boolean isStale) {
                 if (isStale) {
                     fireStale();
                 } else {
                     fireChange();
                 }
             }
         }

         private IObservableSet readableSet;

         private Viewer viewer;

         /**
          * This readableSet returns the same elements as the input readableSet.
          * However, it only fires events AFTER the elements have been added or
          * removed from the viewer.
          */
         private KnownElementsSet knownElements;

         private ISetChangeListener listener = new ISetChangeListener() {

             public void handleSetChange(SetChangeEvent event) {
                 boolean wasStale = knownElements.isStale();
                 if (isDisposed()) {
                     return;
                 }
                 doDiff(event.diff.getAdditions(), event.diff.getRemovals(),
                         true);
                 if (!wasStale && event.getObservableSet().isStale()) {
                     knownElements.doFireStale(true);
                 }
             }
         };

         private IStaleListener staleListener = new IStaleListener() {
             public void handleStale(StaleEvent event) {
                 knownElements.doFireStale(event.getObservable().isStale());
             }
         };

         /**
          *
          */
         public ObservableSetContentProvider() {
             readableSet = new ObservableSet(SWTObservables.getRealm(Display
                     .getDefault()), Collections.EMPTY_SET, Object .class) {
             };
             knownElements = new KnownElementsSet(readableSet);
         }

         public void dispose() {
             setInput(null);
         }

         private void doDiff(Set added, Set removed, boolean updateViewer) {
             knownElements.doFireDiff(added, Collections.EMPTY_SET);

             if (updateViewer) {
                 if (added.size() > 20 || removed.size() > 20) {
                     viewer.refresh();
                 } else {
                     Object [] toAdd = added.toArray();
                     if (viewer instanceof TreeViewer) {
                         TreeViewer tv = (TreeViewer) viewer;
                         tv.add(model, toAdd);
                     } else if (viewer instanceof AbstractListViewer) {
                         AbstractListViewer lv = (AbstractListViewer) viewer;
                         lv.add(toAdd);
                     }
                     Object [] toRemove = removed.toArray();
                     if (viewer instanceof TreeViewer) {
                         TreeViewer tv = (TreeViewer) viewer;
                         tv.remove(toRemove);
                     } else if (viewer instanceof AbstractListViewer) {
                         AbstractListViewer lv = (AbstractListViewer) viewer;
                         lv.remove(toRemove);
                     }
                 }
             }
             knownElements.doFireDiff(Collections.EMPTY_SET, removed);
         }

         public Object [] getElements(Object inputElement) {
             return readableSet.toArray();
         }

         /**
          * Returns the readableSet of elements known to this content provider.
          * Items are added to this readableSet before being added to the viewer,
          * and they are removed after being removed from the viewer. The
          * readableSet is always updated after the viewer. This is intended for
          * use by label providers, as it will always return the items that need
          * labels.
          *
          * @return readableSet of items that will need labels
          */
         public IObservableSet getKnownElements() {
             return knownElements;
         }

         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
             this.viewer = viewer;

             if (newInput != null && !(newInput instanceof IObservableSet)) {
                 throw new IllegalArgumentException (
                         "This content provider only works with input of type IReadableSet"); //$NON-NLS-1$
 }

             setInput((IObservableSet) newInput);
         }

         private boolean isDisposed() {
             return viewer.getControl() == null
                     || viewer.getControl().isDisposed();
         }

         private void setInput(IObservableSet newSet) {
             boolean updateViewer = true;
             if (newSet == null) {
                 newSet = new ObservableSet(SWTObservables.getRealm(Display
                         .getDefault()), Collections.EMPTY_SET, Object .class) {
                 };
                 // don't update the viewer - its input is null
 updateViewer = false;
             }

             boolean wasStale = false;
             if (readableSet != null) {
                 wasStale = readableSet.isStale();
                 readableSet.removeSetChangeListener(listener);
                 readableSet.removeStaleListener(staleListener);
             }

             HashSet additions = new HashSet ();
             HashSet removals = new HashSet ();

             additions.addAll(newSet);
             additions.removeAll(readableSet);

             removals.addAll(readableSet);
             removals.removeAll(newSet);

             readableSet = newSet;

             doDiff(additions, removals, updateViewer);

             if (readableSet != null) {
                 readableSet.addSetChangeListener(listener);
                 readableSet.addStaleListener(staleListener);
             }

             boolean isStale = (readableSet != null && readableSet.isStale());
             if (isStale != wasStale) {
                 knownElements.doFireStale(isStale);
             }
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
          */
         public Object [] getChildren(Object parentElement) {
             // TODO Auto-generated method stub
 return null;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
          */
         public Object getParent(Object element) {
             // TODO Auto-generated method stub
 return null;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
          */
         public boolean hasChildren(Object element) {
             // TODO Auto-generated method stub
 return false;
         }

     }

     /**
      * A FilteredTree that provides a combo which is used to organize and
      * display elements in the tree according to the selected criteria.
      *
      */
     protected class CategoryFilterTree extends FilteredTree {

         private CategoryPatternFilter filter;

         /**
          * Constructor for PatternFilteredTree.
          *
          * @param parent
          * @param treeStyle
          * @param filter
          */
         protected CategoryFilterTree(Composite parent, int treeStyle,
                 CategoryPatternFilter filter) {
             super(parent, treeStyle, filter);
             this.filter = filter;
         }

         public void filterCategories(boolean b) {
             filter.filterCategories(b);
             textChanged();
         }

         public boolean isFilteringCategories() {
             return filter.isFilteringCategories();
         }
     }

     /**
      * A label provider that simply extracts the command name and the formatted
      * trigger sequence from a given binding, and matches them to the correct
      * column.
      */
     private final class BindingLabelProvider extends LabelProvider implements
             ITableLabelProvider {

         /**
          * The index of the column containing the command name.
          */
         private static final int COLUMN_COMMAND = 0;

         /**
          * The index of the column containing the trigger sequence.
          */
         private static final int COLUMN_TRIGGER_SEQUENCE = 1;

         /**
          * The index of the column containing the trigger sequence.
          */
         private static final int COLUMN_WHEN = 2;

         /**
          * The index of the column containing the Category.
          */
         private static final int COLUMN_CATEGORY = 3;

         /**
          * The index of the column with the image for User binding
          */
         private static final int COLUMN_USER = 4;

         /**
          * A resource manager for this preference page.
          */
         private final LocalResourceManager localResourceManager = new LocalResourceManager(
                 JFaceResources.getResources());

         public final void dispose() {
             super.dispose();
             localResourceManager.dispose();
         }

         public final Image getColumnImage(final Object element,
                 final int columnIndex) {
             final Object value = element;
             if (value instanceof Binding) {
                 switch (columnIndex) {
                 case COLUMN_COMMAND:
                     final ParameterizedCommand parameterizedCommand = ((Binding) value)
                             .getParameterizedCommand();
                     if (parameterizedCommand != null) {
                         final String commandId = parameterizedCommand.getId();
                         final ImageDescriptor imageDescriptor = commandImageService
                                 .getImageDescriptor(commandId);
                         if (imageDescriptor == null) {
                             return null;
                         }
                         try {
                             return localResourceManager
                                     .createImage(imageDescriptor);
                         } catch (final DeviceResourceException e) {
                             final String message = "Problem retrieving image for a command '" //$NON-NLS-1$
 + commandId + '\'';
                             final IStatus status = new Status(IStatus.ERROR,
                                     WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
                             WorkbenchPlugin.log(message, status);
                         }
                     }
                     return null;

                 case COLUMN_USER:
                     if (((Binding) value).getType() == Binding.USER)
                         return ImageFactory.getImage("change"); //$NON-NLS-1$
 return ImageFactory.getImage("blank"); //$NON-NLS-1$
 }

             } else if (value instanceof ParameterizedCommand) {
                 switch (columnIndex) {
                 case COLUMN_COMMAND:
                     final ParameterizedCommand parameterizedCommand = (ParameterizedCommand) value;
                     final String commandId = parameterizedCommand.getId();
                     final ImageDescriptor imageDescriptor = commandImageService
                             .getImageDescriptor(commandId);
                     if (imageDescriptor == null) {
                         return null;
                     }
                     try {
                         return localResourceManager
                                 .createImage(imageDescriptor);
                     } catch (final DeviceResourceException e) {
                         final String message = "Problem retrieving image for a command '" //$NON-NLS-1$
 + commandId + '\'';
                         final IStatus status = new Status(IStatus.ERROR,
                                 WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
                         WorkbenchPlugin.log(message, status);
                     }
                     return null;
                 case COLUMN_USER:
                     return ImageFactory.getImage("blank"); //$NON-NLS-1$
 }

             } else if ((value instanceof Category) || (value instanceof String )) {
                 switch (columnIndex) {
                 case COLUMN_COMMAND:
                     final URL url = BundleUtility.find(PlatformUI.PLUGIN_ID,
                             ICON_GROUP_OF_BINDINGS);
                     final ImageDescriptor imageDescriptor = ImageDescriptor
                             .createFromURL(url);
                     try {
                         return localResourceManager
                                 .createImage(imageDescriptor);
                     } catch (final DeviceResourceException e) {
                         final String message = "Problem retrieving image for groups of bindings: '" //$NON-NLS-1$
 + ICON_GROUP_OF_BINDINGS + '\'';
                         final IStatus status = new Status(IStatus.ERROR,
                                 WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
                         WorkbenchPlugin.log(message, status);
                     }
                 }

             }

             return null;
         }

         private boolean checkConflict(Binding binding) {
             Collection matches = (Collection ) localChangeManager
                     .getActiveBindingsDisregardingContext().get(
                             binding.getTriggerSequence());
             if (matches != null) {
                 Iterator i = matches.iterator();
                 while (i.hasNext()) {
                     Binding b = (Binding) i.next();
                     if (binding != b
                             && b.getContextId().equals(binding.getContextId())
                             && b.getSchemeId().equals(binding.getSchemeId())) {
                         return true;
                     }
                 }
             }
             return false;
         }

         public final String getColumnText(final Object element,
                 final int columnIndex) {
             final Object value = element;
             if (value instanceof Binding) {
                 final Binding binding = (Binding) value;
                 switch (columnIndex) {
                 case COLUMN_COMMAND:
                     try {
                         return binding.getParameterizedCommand().getName();

                     } catch (final NotDefinedException e) {
                         return NewKeysPreferenceMessages.Undefined_Command;
                     }
                 case COLUMN_TRIGGER_SEQUENCE:
                     if (checkConflict(binding)) {
                         return "*" + binding.getTriggerSequence().format(); //$NON-NLS-1$
 }
                     return binding.getTriggerSequence().format();

                 case COLUMN_WHEN:
                     try {
                         return contextService
                                 .getContext(binding.getContextId()).getName();
                     } catch (NotDefinedException e1) {
                         return NewKeysPreferenceMessages.Undefined_Context;
                     }
                 case COLUMN_CATEGORY:
                     try {
                         return binding.getParameterizedCommand().getCommand()
                                 .getCategory().getName();
                     } catch (NotDefinedException e) {
                         return NewKeysPreferenceMessages.Unavailable_Category;
                     }
                 default:
                     return null;
                 }
             } else if (value instanceof Category) {
                 if (columnIndex == COLUMN_COMMAND) {
                     try {
                         return ((Category) value).getName();
                     } catch (final NotDefinedException e) {
                         return NewKeysPreferenceMessages.Unavailable_Category;
                     }
                 }

                 return null;

             } else if (value instanceof String ) {
                 // This is a context.
 if (columnIndex == COLUMN_COMMAND) {
                     try {
                         return contextService.getContext((String ) value)
                                 .getName();
                     } catch (final NotDefinedException e) {
                         return NewKeysPreferenceMessages.Undefined_Context;
                     }
                 }

                 return null;
             } else if (value instanceof ParameterizedCommand) {
                 if (columnIndex == COLUMN_COMMAND) {
                     try {
                         return ((ParameterizedCommand) value).getName();
                     } catch (final NotDefinedException e) {
                         return NewKeysPreferenceMessages.Undefined_Command;
                     }
                 }
                 if (columnIndex == COLUMN_TRIGGER_SEQUENCE)
                     return ""; //$NON-NLS-1$

                 if (columnIndex == COLUMN_WHEN)
                     return ""; //$NON-NLS-1$

                 if (columnIndex == COLUMN_CATEGORY) {
                     try {
                         return ((ParameterizedCommand) value).getCommand()
                                 .getCategory().getName();
                     } catch (NotDefinedException e) {
                         return NewKeysPreferenceMessages.Unavailable_Category;
                     }
                 }
                 return null;
             }

             return null;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
          */
         public String getText(Object element) {
             String rc = getColumnText(element, 0);
             if (rc == null) {
                 super.getText(element);
             }
             StringBuffer buf = new StringBuffer (rc);
             for (int i = 1; i < COLUMN_USER; i++) {
                 String text = getColumnText(element, i);
                 if (text != null) {
                     buf.append(' ');
                     buf.append(text);
                 }
             }
             return buf.toString();
         }
     }

     /**
      * Sorts the bindings in the filtered tree based on the current grouping.
      */
     private final class BindingComparator extends ViewerComparator {

         private int sortColumn = 0;

         private int lastSortColumn = 0;

         private boolean ascending = true;

         public final int category(final Object element) {
             switch (grouping) {
             case GROUPING_CATEGORY:
                 // TODO This has to be done with something other than the hash.
 try {
                     final ParameterizedCommand command = (element instanceof ParameterizedCommand) ? (ParameterizedCommand) element
                             : ((Binding) element).getParameterizedCommand();
                     return command.getCommand().getCategory().hashCode();
                 } catch (final NotDefinedException e) {
                     return 0;
                 }
             case GROUPING_CONTEXT:
                 // TODO This has to be done with something other than the hash.
 if (element instanceof Binding) {
                     return ((Binding) element).getContextId().hashCode();
                 }
             case GROUPING_NONE:
             default:
                 return 0;
             }
         }

         public final int compare(final Viewer viewer, final Object a,
                 final Object b) {

             int result = compareColumn(viewer, a, b, sortColumn);
             if (result == 0 && sortColumn != lastSortColumn) {
                 result = compareColumn(viewer, a, b, lastSortColumn);
             }
             return ascending ? result : (-1) * result;
         }
         
         private int compareColumn(final Viewer viewer, final Object a, final Object b,
                 final int columnNumber) {
             if (columnNumber == BindingLabelProvider.COLUMN_USER) {
                 return sortUser(viewer, a, b);
             }
             IBaseLabelProvider baseLabel = ((TreeViewer)viewer).getLabelProvider();
             if (baseLabel instanceof ITableLabelProvider) {
                 ITableLabelProvider tableProvider = (ITableLabelProvider) baseLabel;
                 String e1p = tableProvider.getColumnText(a, columnNumber);
                 String e2p = tableProvider.getColumnText(b, columnNumber);
                 if (e1p != null && e2p != null) {
                     return getComparator().compare(e1p, e2p);
                 }
             }
             return 0;
         }
         
         private int sortUser(final Viewer viewer, final Object a, final Object b) {
             int typeA = (a instanceof Binding?((Binding)a).getType():Binding.SYSTEM);
             int typeB = (b instanceof Binding?((Binding)b).getType():Binding.SYSTEM);
             int result = typeA - typeB;
             return result;
         }

         /**
          * @return Returns the sortColumn.
          */
         public int getSortColumn() {
             return sortColumn;
         }

         /**
          * @param sortColumn
          * The sortColumn to set.
          */
         public void setSortColumn(int sortColumn) {
             lastSortColumn = this.sortColumn;
             if (lastSortColumn != sortColumn) {
                 ascending = true;
             }
             this.sortColumn = sortColumn;
         }

         /**
          * @return Returns the ascending.
          */
         public boolean isAscending() {
             return ascending;
         }

         /**
          * @param ascending
          * The ascending to set.
          */
         public void setAscending(boolean ascending) {
             this.ascending = ascending;
         }
     }

     /**
      * The constant value for <code>grouping</code> when the bindings should
      * be grouped by category.
      */
     private static final int GROUPING_CATEGORY = 0;

     /**
      * The constant value for <code>grouping</code> when the bindings should
      * be grouped by context.
      */
     private static final int GROUPING_CONTEXT = 1;

     /**
      * The constant value for <code>grouping</code> when the bindings should
      * not be grouped (i.e., they should be displayed in a flat list).
      */
     private static final int GROUPING_NONE = 2;

     /**
      * The path at which the icon for "groups of bindings" is located.
      */
     private static final String ICON_GROUP_OF_BINDINGS = "$nl$/icons/full/obj16/keygroups_obj.gif"; //$NON-NLS-1$

     private static final String CONTEXT_ID_ACTION_SETS = "org.eclipse.ui.contexts.actionSet"; //$NON-NLS-1$

     private static final String CONTEXT_ID_INTERNAL = ".internal."; //$NON-NLS-1$

     /**
      * The number of items to show in the bindings table tree.
      */
     private static final int ITEMS_TO_SHOW = 7;

     /**
      * A comparator that can be used for display of
      * <code>NamedHandleObject</code> instances to the end user.
      */
     private static final NamedHandleObjectComparator NAMED_HANDLE_OBJECT_COMPARATOR = new NamedHandleObjectComparator();

     public final static String TAG_DIALOG_SECTION = "org.eclipse.ui.preferences.keysPreferencePage"; //$NON-NLS-1$

     private final String TAG_FIELD = "showAllField"; //$NON-NLS-1$

     private static final String TAG_FILTER_ACTION_SETS = "actionSetFilter"; //$NON-NLS-1$

     private static final String TAG_FILTER_INTERNAL = "internalFilter"; //$NON-NLS-1$

     private static final String TAG_FILTER_UNCAT = "uncategorizedFilter"; //$NON-NLS-1$

     /**
      * Sorts the given array of <code>NamedHandleObject</code> instances based
      * on their name. This is generally useful if they will be displayed to an
      * end users.
      *
      * @param objects
      * The objects to be sorted; must not be <code>null</code>.
      * @return The same array, but sorted in place; never <code>null</code>.
      */
     private static final NamedHandleObject[] sortByName(
             final NamedHandleObject[] objects) {
         Arrays.sort(objects, NAMED_HANDLE_OBJECT_COMPARATOR);
         return objects;
     }

     /**
      * The workbench's binding service. This binding service is used to access
      * the current set of bindings, and to persist changes.
      */
     private IBindingService bindingService;

     /**
      * The text widget containing the key sequence. This value is
      * <code>null</code> until the controls are created.
      */
     private Text bindingText;

     /**
      * The workbench's command image service. This command image service is used
      * to provide an icon beside each command.
      */
     private ICommandImageService commandImageService;

     /**
      * The label containing the name of the currently selected binding's
      * command. This value is <code>null</code> until the controls are
      * created.
      */
     private Label commandNameValueLabel;

     /**
      * The workbench's command service. This command service is used to access
      * the list of commands.
      */
     private ICommandService commandService;

     /**
      * The workbench's context service. This context service is used to access
      * the list of contexts.
      */
     private IContextService contextService;

     /**
      * The label containing the description of the currently selected binding's
      * command. This value is <code>null</code> until the controls are
      * created.
      */
     private Text descriptionValueText;

     /**
      * The filtered tree containing the list of commands and bindings to edit.
      */
     private CategoryFilterTree filteredTree;

     private CategoryPatternFilter patternFilter;

     /**
      * The grouping for the bindings tree. Either there should be no group
      * (i.e., flat list), or the bindings should be grouped by either category
      * or context.
      */
     private int grouping = GROUPING_NONE;

     /**
      * The key sequence entry widget containing the trigger sequence for the
      * currently selected binding. This value is <code>null</code> until the
      * controls are created.
      */
     private KeySequenceText keySequenceText;

     /**
      * A binding manager local to this preference page. When the page is
      * initialized, the current bindings are read out from the binding service
      * and placed in this manager. This manager is then updated as the user
      * makes changes. When the user has finished, the contents of this manager
      * are compared with the contents of the binding service. The changes are
      * then persisted.
      */
     private BindingManager localChangeManager;

     /**
      * The context id of the binding which the user is trying to add. This value
      * is derived from the binding that is selected at the time the user tried
      * to add a binding. If this value is <code>null</code>, then the user is
      * not currently trying to add a binding to a command that already has a
      * binding.
      */
     private String markedContextId = null;

     /**
      * The parameterized command to which the user is currently trying to add a
      * binding. If this value is <code>null</code>, then the user is not
      * currently trying to add a binding to a command that already has a
      * binding.
      */
     private ParameterizedCommand markedParameterizedCommand = null;

     /**
      * The combo box containing the list of possible schemes to choose from.
      * This value is <code>null</code> until the contents are created.
      */
     private ComboViewer schemeCombo = null;

     /**
      * The check box controlling whether all commands should be shown in the
      * filtered tree. This value is <code>null</code> until the contents are
      * created.
      */
     private Button showAllCheckBox = null;

     private boolean filterActionSetContexts = true;

     private boolean filterInternalContexts = true;

     private IObservableSet commandModel;

     private IObservableSet bindingModel;

     private UnionSet model;

     /**
      * The combo box containing the list of possible contexts to choose from.
      * This value is <code>null</code> until the contents are create.
      */
     private ComboViewer whenCombo = null;

     /**
      * Adds a new binding based on an existing binding. The command and the
      * context are copied from the existing binding. The scheme id is set to be
      * the user's personal derivative scheme. The preference page is updated,
      * and focus is placed in the key sequence field.
      *
      * @param binding
      * The binding to be added; must not be <code>null</code>.
      */
     private final void bindingAdd(final Binding binding) {
         if (!(binding.getParameterizedCommand().getCommand().isDefined()))
             return;

         // Remember the parameterized command and context.
 markedParameterizedCommand = binding.getParameterizedCommand();
         markedContextId = binding.getContextId();

         // Update the preference page.
 update();

         // Select the new binding.
 filteredTree.getViewer().setSelection(
                 new StructuredSelection(binding.getParameterizedCommand()),
                 true);
         bindingText.setFocus();
         bindingText.selectAll();
     }

     /**
      * Removes an existing binding. The preference page is then updated.
      *
     * @param binding
     * The binding to be removed; must not be <code>null</code>.
     */
    private final void bindingRemove(final KeyBinding binding) {
        ArrayList extraSystemDeletes = new ArrayList ();
        final String contextId = binding.getContextId();
        final String schemeId = binding.getSchemeId();
        final KeySequence triggerSequence = binding.getKeySequence();
        if (binding.getType() == Binding.USER) {
            localChangeManager.removeBinding(binding);
        } else {
            // TODO This should be the user's personal scheme.
 Collection previousConflictMatches = (Collection ) localChangeManager
                    .getActiveBindingsDisregardingContext().get(
                            binding.getTriggerSequence());
            KeyBinding deleteBinding = new KeyBinding(triggerSequence, null,
                    schemeId, contextId, null, null, null, Binding.USER);
            localChangeManager.addBinding(deleteBinding);
            if (previousConflictMatches != null) {
                Iterator i = previousConflictMatches.iterator();
                while (i.hasNext()) {
                    Binding b = (Binding) i.next();
                    if (b != binding && deletes(deleteBinding, b)) {
                        extraSystemDeletes.add(b);
                    }
                }
            }
        }

        // update the model
 bindingModel.remove(binding);
        bindingAdd(binding);
        if (!extraSystemDeletes.isEmpty()) {
            Iterator i = extraSystemDeletes.iterator();
            while (i.hasNext()) {
                KeyBinding b = (KeyBinding) i.next();
                KeyBinding newBinding = new KeyBinding(b.getKeySequence(), b
                        .getParameterizedCommand(), b.getSchemeId(), b
                        .getContextId(), null, null, null, Binding.USER);
                localChangeManager.addBinding(newBinding);

                bindingModel.remove(b);
                bindingModel.add(newBinding);
            }
        }
        updateConflicts(binding);
    }

    private final void updateConflicts(final Collection bindings) {
        Iterator i = bindings.iterator();
        while (i.hasNext()) {
            final Binding b = (Binding) i.next();
            if (b.getParameterizedCommand()!=null) {
                updateConflicts(b);
            }
        }
    }

    private final void updateConflicts(final Binding binding) {
        Collection matches = (Collection ) localChangeManager
                .getActiveBindingsDisregardingContext().get(
                        binding.getTriggerSequence());
        if (matches != null) {
            Iterator i = matches.iterator();
            while (i.hasNext()) {
                Binding b = (Binding) i.next();
                if (binding != b
                        && b.getContextId().equals(binding.getContextId())) {
                    filteredTree.getViewer().update(b, null);
                }
            }
        }
    }

    private final void bindingRestore(final KeyBinding binding) {
        final ParameterizedCommand cmd = binding.getParameterizedCommand();
        bindingRestore(cmd, false);
    }

    private String locale = Locale.getDefault().toString();

    private boolean localMatches(String l) {
        if (l == null) {
            return true;
        }
        return Util.equals(locale, l);
    }

    private String platform = SWT.getPlatform();

    private boolean platformMatches(String p) {
        if (p == null) {
            return true;
        }
        return Util.equals(platform, p);
    }
    
    private final String [] getSchemeIds(String schemeId) {
        final List strings = new ArrayList ();
        while (schemeId != null) {
            strings.add(schemeId);
            try {
                schemeId = bindingService.getScheme(schemeId).getParentId();
            } catch (final NotDefinedException e) {
                return new String [0];
            }
        }

        return (String []) strings.toArray(new String [strings.size()]);
    }

    private final void bindingRestore(final ParameterizedCommand cmd,
            boolean removeCmd) {
        Set addSystemAll = new HashSet ();
        ArrayList removeUser = new ArrayList ();
        ArrayList removeBinding = new ArrayList ();
        Binding[] bindings = localChangeManager.getBindings();
        for (int i = 0; i < bindings.length; i++) {
            final Binding b = bindings[i];
            if (b.getParameterizedCommand() == null
                    && localMatches(b.getLocale())
                    && platformMatches(b.getPlatform())) {
                // flat out, a delete marker
 removeBinding.add(b);
            } else if (cmd.equals(b.getParameterizedCommand())) {
                if (b.getType() == Binding.SYSTEM
                        && localMatches(b.getLocale())
                        && platformMatches(b.getPlatform())) {
                    // a system binding for this command
 addSystemAll.add(b);
                } else if (b.getType() == Binding.USER) {
                    // a user binding for this command
 removeUser.add(b);
                    localChangeManager.removeBinding(b);
                }
            }
        }

        if (!addSystemAll.isEmpty()) {
            String [] activeSchemeIds = getSchemeIds(getSchemeId());
            Binding[] sysArray = (Binding[]) addSystemAll
                    .toArray(new Binding[addSystemAll.size()]);
            for (int k = 0; k < sysArray.length; k++) {
                Binding sys = sysArray[k];
                boolean deleted = false;
                for (Iterator i = removeBinding.iterator(); i.hasNext();) {
                    Binding del = (Binding) i.next();
                    if (deletes(del, sys)) {
                        if (del.getType() == Binding.USER) {
                            removeUser.add(del);
                            localChangeManager.removeBinding(del);
                        } else {
                            deleted = true;
                            addSystemAll.remove(sys);
                        }
                    }
                }
                // Check the scheme ids.
 final String schemeId = sys.getSchemeId();
                boolean found = false;
                if (activeSchemeIds != null && !deleted) {
                    for (int j = 0; j < activeSchemeIds.length; j++) {
                        if (Util.equals(schemeId, activeSchemeIds[j])) {
                            found = true;
                            break;
                        }
                    }
                }
                if (!found && sys.getType() == Binding.SYSTEM) {
                    addSystemAll.remove(sys);
                }
            }
        }
        

        bindingModel.addAll(addSystemAll);
        bindingModel.removeAll(removeUser);
        updateConflicts(addSystemAll);
        updateConflicts(removeUser);
        if (addSystemAll.isEmpty()) {
            commandModel.add(cmd);
            filteredTree.getViewer().setSelection(new StructuredSelection(cmd),
                    true);
        } else if (removeCmd) {
            commandModel.remove(cmd);
        }
        if (!addSystemAll.isEmpty()) {
            // Select the new binding.
 filteredTree.getViewer().setSelection(
                    new StructuredSelection(addSystemAll.iterator().next()),
                    true);
        }

        update();
    }

    final static boolean deletes(final Binding del, final Binding binding) {
        boolean deletes = true;
        deletes &= Util.equals(del.getContextId(), binding.getContextId());
        deletes &= Util.equals(del.getTriggerSequence(), binding
                .getTriggerSequence());
        if (del.getLocale() != null) {
            deletes &= Util.equals(del.getLocale(), binding.getLocale());
        }
        if (del.getPlatform() != null) {
            deletes &= Util.equals(del.getPlatform(), binding.getPlatform());
        }
        deletes &= (binding.getType() == Binding.SYSTEM);
        deletes &= Util.equals(del.getParameterizedCommand(), null);

        return deletes;
    }

    /**
     * Creates the button bar across the bottom of the preference page. This
     * button bar contains the "Advanced..." button.
     *
     * @param parent
     * The composite in which the button bar should be placed; never
     * <code>null</code>.
     * @return The button bar composite; never <code>null</code>.
     */
    private final Control createButtonBar(final Composite parent) {
        GridLayout layout;
        GridData gridData;
        int widthHint;

        // Create the composite to house the button bar.
 final Composite buttonBar = new Composite(parent, SWT.NONE);
        layout = new GridLayout(1, false);
        layout.marginWidth = 0;
        buttonBar.setLayout(layout);
        gridData = new GridData();
        gridData.horizontalAlignment = SWT.END;
        buttonBar.setLayoutData(gridData);

        // Advanced button.
 final Button advancedButton = new Button(buttonBar, SWT.PUSH);
        gridData = new GridData();
        widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
        advancedButton.setText(NewKeysPreferenceMessages.AdvancedButton_Text);
        gridData.widthHint = Math.max(widthHint, advancedButton.computeSize(
                SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
        advancedButton.setLayoutData(gridData);
        advancedButton.addSelectionListener(new SelectionListener() {
            public void widgetDefaultSelected(SelectionEvent e) {
            }

            public void widgetSelected(SelectionEvent e) {
                KeysPreferenceFiltersDialog dialog = new KeysPreferenceFiltersDialog(
                        getShell());
                dialog.setFilterActionSet(filterActionSetContexts);
                dialog.setFilterInternal(filterInternalContexts);
                dialog.setFilterUncategorized(filteredTree.isFilteringCategories());
                if (dialog.open() == Window.OK) {
                    filterActionSetContexts = dialog.getFilterActionSet();
                    filterInternalContexts = dialog.getFilterInternal();
                    filteredTree.filterCategories(dialog
                            .getFilterUncategorized());
                    whenCombo.setInput(getContexts());
                    updateDataControls();
                }
            }
        });
        return buttonBar;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
     */
    protected final Control createContents(final Composite parent) {
        PlatformUI.getWorkbench().getHelpSystem().setHelp(parent,
                IWorkbenchHelpContextIds.KEYS_PREFERENCE_PAGE);
        
        GridLayout layout = null;

        long startTime = 0L;
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }

        IDialogSettings settings = getDialogSettings();
        if (settings.get(TAG_FILTER_ACTION_SETS) != null) {
            filterActionSetContexts = settings
                    .getBoolean(TAG_FILTER_ACTION_SETS);
        }
        if (settings.get(TAG_FILTER_INTERNAL) != null) {
            filterInternalContexts = settings.getBoolean(TAG_FILTER_INTERNAL);
        }
        patternFilter = new CategoryPatternFilter(
                true, commandService.getCategory(null));
        if (settings.get(TAG_FILTER_UNCAT) != null) {
            patternFilter.filterCategories(settings
                    .getBoolean(TAG_FILTER_UNCAT));
        }

        // Creates a composite to hold all of the page contents.
 final Composite page = new Composite(parent, SWT.NONE);
        layout = new GridLayout(1, false);
        layout.marginWidth = 0;
        page.setLayout(layout);

        createSchemeControls(page);
        createTree(page);
        createTreeControls(page);
        createDataControls(page);
        createButtonBar(page);

        fill();
        update();

        applyDialogFont(page);

        if (DEBUG) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Tracing.printTrace(TRACING_COMPONENT, "Created page in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }

        return page;
    }

    private final Control createDataControls(final Composite parent) {
        GridLayout layout;
        GridData gridData;

        // Creates the data area.
 final Composite dataArea = new Composite(parent, SWT.NONE);
        layout = new GridLayout(2, true);
        layout.marginWidth = 0;
        dataArea.setLayout(layout);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        dataArea.setLayoutData(gridData);

        // LEFT DATA AREA
 // Creates the left data area.
 final Composite leftDataArea = new Composite(dataArea, SWT.NONE);
        layout = new GridLayout(3, false);
        leftDataArea.setLayout(layout);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.verticalAlignment = SWT.TOP;
        gridData.horizontalAlignment = SWT.FILL;
        leftDataArea.setLayoutData(gridData);

        // The command name label.
 final Label commandNameLabel = new Label(leftDataArea, SWT.NONE);
        commandNameLabel
                .setText(NewKeysPreferenceMessages.CommandNameLabel_Text);

        // The current command name.
 commandNameValueLabel = new Label(leftDataArea, SWT.NONE);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalSpan = 2;
        gridData.horizontalAlignment = SWT.FILL;
        commandNameValueLabel.setLayoutData(gridData);

        // The binding label.
 final Label bindingLabel = new Label(leftDataArea, SWT.NONE);
        bindingLabel.setText(NewKeysPreferenceMessages.BindingLabel_Text);

        // The key sequence entry widget.
 bindingText = new Text(leftDataArea, SWT.BORDER);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        gridData.widthHint = 200;
        bindingText.setLayoutData(gridData);

        bindingText.addFocusListener(new FocusListener() {
            public void focusGained(FocusEvent e) {
                bindingService.setKeyFilterEnabled(false);
            }

            public void focusLost(FocusEvent e) {
                bindingService.setKeyFilterEnabled(true);
            }
        });
        bindingText.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                if (!bindingService.isKeyFilterEnabled()) {
                    bindingService.setKeyFilterEnabled(true);
                }
            }
        });

        keySequenceText = new KeySequenceText(bindingText);
        keySequenceText.setKeyStrokeLimit(4);
        keySequenceText
                .addPropertyChangeListener(new IPropertyChangeListener() {
                    public final void propertyChange(
                            final PropertyChangeEvent event) {
                        if (!event.getOldValue().equals(event.getNewValue())) {
                            keySequenceChanged();
                        }
                    }
                });

        // Button for adding trapped key strokes
 final Button addKeyButton = new Button(leftDataArea, SWT.LEFT
                | SWT.ARROW);
        addKeyButton
                .setToolTipText(NewKeysPreferenceMessages.AddKeyButton_ToolTipText);
        gridData = new GridData();
        gridData.heightHint = schemeCombo.getCombo().getTextHeight();
        addKeyButton.setLayoutData(gridData);

        // Arrow buttons aren't normally added to the tab list. Let's fix that.
 final Control[] tabStops = dataArea.getTabList();
        final ArrayList newTabStops = new ArrayList ();
        for (int i = 0; i < tabStops.length; i++) {
            Control tabStop = tabStops[i];
            newTabStops.add(tabStop);
            if (bindingText.equals(tabStop)) {
                newTabStops.add(addKeyButton);
            }
        }
        final Control[] newTabStopArray = (Control[]) newTabStops
                .toArray(new Control[newTabStops.size()]);
        dataArea.setTabList(newTabStopArray);

        // Construct the menu to attach to the above button.
 final Menu addKeyMenu = new Menu(addKeyButton);
        final Iterator trappedKeyItr = KeySequenceText.TRAPPED_KEYS.iterator();
        while (trappedKeyItr.hasNext()) {
            final KeyStroke trappedKey = (KeyStroke) trappedKeyItr.next();
            final MenuItem menuItem = new MenuItem(addKeyMenu, SWT.PUSH);
            menuItem.setText(trappedKey.format());
            menuItem.addSelectionListener(new SelectionAdapter() {

                public void widgetSelected(SelectionEvent e) {
                    keySequenceText.insert(trappedKey);
                    bindingText.setFocus();
                    bindingText.setSelection(bindingText.getTextLimit());
                }
            });
        }
        addKeyButton.addSelectionListener(new SelectionAdapter() {

            public void widgetSelected(SelectionEvent selectionEvent) {
                Point buttonLocation = addKeyButton.getLocation();
                buttonLocation = dataArea.toDisplay(buttonLocation.x,
                        buttonLocation.y);
                Point buttonSize = addKeyButton.getSize();
                addKeyMenu.setLocation(buttonLocation.x, buttonLocation.y
                        + buttonSize.y);
                addKeyMenu.setVisible(true);
            }
        });

        final IObservableValue selection = ViewersObservables
                .observeSingleSelection(filteredTree.getViewer());

        // The when label.
 final Label whenLabel = new Label(leftDataArea, SWT.NONE);
        whenLabel.setText(NewKeysPreferenceMessages.WhenLabel_Text);

        // The when combo.
 whenCombo = new ComboViewer(leftDataArea);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        gridData.horizontalSpan = 2;
        whenCombo.getCombo().setLayoutData(gridData);
        whenCombo.setLabelProvider(new NamedHandleObjectLabelProvider());
        whenCombo.setContentProvider(new ArrayContentProvider());
        whenCombo.setComparator(new ViewerComparator());
        whenCombo
                .addPostSelectionChangedListener(new ISelectionChangedListener() {

                    public void selectionChanged(SelectionChangedEvent event) {
                        updateWhenCombo();
                    }
                });

        whenCombo.getCombo().setVisibleItemCount(20);
        whenCombo.getCombo().setVisible(false);
        whenLabel.setVisible(false);
        selection.addValueChangeListener(new IValueChangeListener() {

            public void handleValueChange(ValueChangeEvent event) {
                boolean visible = false;
                if (selection.getValue() instanceof KeyBinding) {
                    visible = true;
                }
                Combo combo = whenCombo.getCombo();
                if (!combo.isDisposed()) {
                    combo.setVisible(visible);
                }
                if (!whenLabel.isDisposed()) {
                    whenLabel.setVisible(visible);
                }
            }
        });
        
        final Label asterisk = new Label(leftDataArea, SWT.NONE);
        asterisk.setText(NewKeysPreferenceMessages.Asterisk_Text);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalSpan = 2;
        gridData.horizontalAlignment = SWT.FILL;
        asterisk.setLayoutData(gridData);

        // RIGHT DATA AREA
 // Creates the right data area.
 final Composite rightDataArea = new Composite(dataArea, SWT.NONE);
        layout = new GridLayout(1, false);
        rightDataArea.setLayout(layout);
        gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        rightDataArea.setLayoutData(gridData);

        // The description label.
 final Label descriptionLabel = new Label(rightDataArea, SWT.NONE);
        descriptionLabel
                .setText(NewKeysPreferenceMessages.DescriptionLabel_Text);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        descriptionLabel.setLayoutData(gridData);

        // The description value.
 descriptionValueText = new Text(rightDataArea, SWT.BORDER | SWT.MULTI
                | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL | SWT.H_SCROLL);
        gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        gridData.horizontalIndent = 20;
        descriptionValueText.setLayoutData(gridData);
        return dataArea;
    }

    private final Control createSchemeControls(final Composite parent) {
        GridLayout layout;
        GridData gridData;

        // Create a composite to hold the controls.
 final Composite schemeControls = new Composite(parent, SWT.NONE);
        layout = new GridLayout(3, false);
        layout.marginWidth = 0;
        schemeControls.setLayout(layout);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        schemeControls.setLayoutData(gridData);

        // Create the label.
 final Label schemeLabel = new Label(schemeControls, SWT.NONE);
        schemeLabel.setText(NewKeysPreferenceMessages.SchemeLabel_Text);

        // Create the combo.
 schemeCombo = new ComboViewer(schemeControls);
        schemeCombo.setLabelProvider(new NamedHandleObjectLabelProvider());
        schemeCombo.setContentProvider(new ArrayContentProvider());
        gridData = new GridData();
        gridData.widthHint = 150;
        gridData.horizontalAlignment = SWT.FILL;
        schemeCombo.getCombo().setLayoutData(gridData);
        schemeCombo
                .addSelectionChangedListener(new ISelectionChangedListener() {
                    public final void selectionChanged(
                            final SelectionChangedEvent event) {
                        selectSchemeCombo(event);
                    }
                });

        return schemeControls;
    }

    private final Control createTree(final Composite parent) {
        GridData gridData;

        filteredTree = new CategoryFilterTree(parent, SWT.SINGLE
                | SWT.FULL_SELECTION | SWT.BORDER, patternFilter);
        final GridLayout layout = new GridLayout(1, false);
        layout.marginWidth = 0;
        filteredTree.setLayout(layout);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.grabExcessVerticalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        gridData.verticalAlignment = SWT.FILL;
        filteredTree.setLayoutData(gridData);

        // Make sure the filtered tree has a height of ITEMS_TO_SHOW
 final Tree tree = filteredTree.getViewer().getTree();
        tree.setHeaderVisible(true);
        final Object layoutData = tree.getLayoutData();
        if (layoutData instanceof GridData) {
            gridData = (GridData) layoutData;
            final int itemHeight = tree.getItemHeight();
            if (itemHeight > 1) {
                gridData.heightHint = ITEMS_TO_SHOW * itemHeight;
            }
        }

        final BindingComparator comparator = new BindingComparator();
        comparator.setSortColumn(0);

        // Create the columns for the tree.

        final TreeColumn commandNameColumn = new TreeColumn(tree, SWT.LEFT,
                BindingLabelProvider.COLUMN_COMMAND);
        commandNameColumn
                .setText(NewKeysPreferenceMessages.CommandNameColumn_Text);
        tree.setSortColumn(commandNameColumn);
        tree.setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
        commandNameColumn.addSelectionListener(new ResortColumn(comparator,
                commandNameColumn, tree, BindingLabelProvider.COLUMN_COMMAND));

        final TreeColumn triggerSequenceColumn = new TreeColumn(tree, SWT.LEFT,
                BindingLabelProvider.COLUMN_TRIGGER_SEQUENCE);
        triggerSequenceColumn
                .setText(NewKeysPreferenceMessages.TriggerSequenceColumn_Text);
        triggerSequenceColumn.addSelectionListener(new ResortColumn(comparator,
                triggerSequenceColumn, tree,
                BindingLabelProvider.COLUMN_TRIGGER_SEQUENCE));

        final TreeColumn whenColumn = new TreeColumn(tree, SWT.LEFT,
                BindingLabelProvider.COLUMN_WHEN);
        whenColumn.setText(NewKeysPreferenceMessages.WhenColumn_Text);
        whenColumn.addSelectionListener(new ResortColumn(comparator,
                whenColumn, tree, BindingLabelProvider.COLUMN_WHEN));

        final TreeColumn categoryColumn = new TreeColumn(tree, SWT.LEFT,
                BindingLabelProvider.COLUMN_CATEGORY);
        categoryColumn.setText(NewKeysPreferenceMessages.CategoryColumn_Text);
        categoryColumn.addSelectionListener(new ResortColumn(comparator,
                categoryColumn, tree, BindingLabelProvider.COLUMN_CATEGORY));

        final TreeColumn userMarker = new TreeColumn(tree, SWT.LEFT,
                BindingLabelProvider.COLUMN_USER);
        userMarker.setText(NewKeysPreferenceMessages.UserColumn_Text);
        userMarker.addSelectionListener(new ResortColumn(comparator,
                userMarker, tree, BindingLabelProvider.COLUMN_USER));

        // Set up the providers for the viewer.
 final TreeViewer viewer = filteredTree.getViewer();
        viewer.setLabelProvider(new BindingLabelProvider());

        viewer.setContentProvider(new ObservableSetContentProvider());

        viewer.setComparator(comparator);

        /*
         * Listen for selection changes so that the data controls can be
         * updated.
         */
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public final void selectionChanged(final SelectionChangedEvent event) {
                selectTreeRow(event);
            }
        });

        // Adjust how the filter works.
 filteredTree.getPatternFilter().setIncludeLeadingWildcard(true);
        return filteredTree;
    }

    private final Control createTreeControls(final Composite parent) {
        GridLayout layout;
        GridData gridData;
        int widthHint;

        // Creates controls related to the tree.
 final Composite treeControls = new Composite(parent, SWT.NONE);
        layout = new GridLayout(4, false);
        layout.marginWidth = 0;
        treeControls.setLayout(layout);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        treeControls.setLayoutData(gridData);

        // Create the show all check box.
 showAllCheckBox = new Button(treeControls, SWT.CHECK);
        gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        gridData.verticalAlignment = SWT.TOP;
        showAllCheckBox.setLayoutData(gridData);
        showAllCheckBox.setText(NewKeysPreferenceMessages.ShowAllCheckBox_Text);
        IDialogSettings settings = getDialogSettings();
        showAllCheckBox.setSelection(settings.getBoolean(TAG_FIELD));
        showAllCheckBox.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                updateShowAll();
            }
        });

        final IObservableValue selection = ViewersObservables
                .observeSingleSelection(filteredTree.getViewer());

        // Create the delete binding button.
 final Button addBindingButton = new Button(treeControls, SWT.PUSH);
        gridData = new GridData();
        widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
        addBindingButton
                .setText(NewKeysPreferenceMessages.AddBindingButton_Text);
        gridData.widthHint = Math.max(widthHint, addBindingButton.computeSize(
                SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
        addBindingButton.setLayoutData(gridData);
        addBindingButton.addSelectionListener(new SelectionAdapter() {
            public final void widgetSelected(final SelectionEvent event) {
                selectAddBindingButton(event);
            }
        });
        new ControlUpdater(addBindingButton) {
            protected void updateControl() {
                Object selectedObject = selection.getValue();
                addBindingButton
                        .setEnabled(selectedObject instanceof KeyBinding);
            }
        };

        // Create the delete binding button.
 final Button removeBindingButton = new Button(treeControls, SWT.PUSH);
        gridData = new GridData();
        widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
        removeBindingButton
                .setText(NewKeysPreferenceMessages.RemoveBindingButton_Text);
        gridData.widthHint = Math.max(widthHint, removeBindingButton
                .computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
        removeBindingButton.setLayoutData(gridData);
        removeBindingButton.addSelectionListener(new SelectionAdapter() {
            public final void widgetSelected(final SelectionEvent event) {
                selectRemoveBindingButton(event);
            }
        });
        new ControlUpdater(removeBindingButton) {
            protected void updateControl() {
                Object selectedObject = selection.getValue();
                removeBindingButton
                        .setEnabled(selectedObject instanceof KeyBinding);
            }
        };

        // Create the delete binding button.
 final Button restore = new Button(treeControls, SWT.PUSH);
        gridData = new GridData();
        widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
        restore.setText(NewKeysPreferenceMessages.RestoreBindingButton_Text);
        gridData.widthHint = Math.max(widthHint, restore.computeSize(
                SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
        restore.setLayoutData(gridData);
        restore.addSelectionListener(new SelectionAdapter() {
            public final void widgetSelected(final SelectionEvent event) {
                selectRestoreBindingButton(event);
            }
        });

        return treeControls;
    }

    private void updateShowAll() {
        BusyIndicator.showWhile(
                filteredTree.getViewer().getTree().getDisplay(),
                new Runnable () {
                    public void run() {
                        try {
                            filteredTree.getViewer().getTree().setRedraw(false);
                            fillInCommands();
                        } finally {
                            filteredTree.getViewer().getTree().setRedraw(true);
                        }
                    }
                });
    }

    /**
     * Copies all of the information from the workbench into a local change
     * manager, and then the local change manager is used to populate the
     * contents of the various widgets on the page.
     *
     * The widgets affected by this method are: scheme combo, bindings
     * table/tree model, and the when combo.
     */
    private final void fill() {
        // Make an internal binding manager to track changes.
 localChangeManager = new BindingManager(new ContextManager(),
                new CommandManager());
        final Scheme[] definedSchemes = bindingService.getDefinedSchemes();
        try {
            for (int i = 0; i < definedSchemes.length; i++) {
                final Scheme scheme = definedSchemes[i];
                final Scheme copy = localChangeManager
                        .getScheme(scheme.getId());
                copy.define(scheme.getName(), scheme.getDescription(), scheme
                        .getParentId());
            }
            localChangeManager
                    .setActiveScheme(bindingService.getActiveScheme());
        } catch (final NotDefinedException e) {
            throw new Error (
                    "There is a programmer error in the keys preference page"); //$NON-NLS-1$
 }
        localChangeManager.setLocale(bindingService.getLocale());
        localChangeManager.setPlatform(bindingService.getPlatform());
        localChangeManager.setBindings(bindingService.getBindings());

        // Update the scheme combo.
 schemeCombo
                .setInput(sortByName(localChangeManager.getDefinedSchemes()));
        setScheme(localChangeManager.getActiveScheme());

        // Update the when combo.
 whenCombo.setInput(getContexts());

        commandModel = new WritableSet();
        bindingModel = new WritableSet();
        model = new UnionSet(
                new IObservableSet[] { bindingModel, commandModel });

        bindingModel.addAll(localChangeManager
                .getActiveBindingsDisregardingContextFlat());
        fillInCommands();

        if (DEBUG) {
            Tracing.printTrace(TRACING_COMPONENT,
                    "fill in size: " + model.size()); //$NON-NLS-1$
 }

        filteredTree.getViewer().setInput(model);
    }

    /**
     *
     */
    private void fillInCommands() {
        long startTime = 0L;
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }
        if (showAllCheckBox.getSelection()) {
            final Collection commandIds = commandService.getDefinedCommandIds();
            final Collection commands = new HashSet ();
            final Iterator commandIdItr = commandIds.iterator();
            while (commandIdItr.hasNext()) {
                final String currentCommandId = (String ) commandIdItr.next();
                final Command currentCommand = commandService
                        .getCommand(currentCommandId);
                try {
                    commands.addAll(ParameterizedCommand
                            .generateCombinations(currentCommand));
                } catch (final NotDefinedException e) {
                    // It is safe to just ignore undefined commands.
 }
            }

            // Remove duplicates.
 Iterator i = bindingModel.iterator();
            while (i.hasNext()) {
                commands.remove(((Binding) i.next()).getParameterizedCommand());
            }

            commandModel.addAll(commands);
        } else {
            commandModel.clear();
        }

        if (DEBUG) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Tracing.printTrace(TRACING_COMPONENT, "fillInCommands in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
     */
    public final void init(final IWorkbench workbench) {
        bindingService = (IBindingService) workbench
                .getService(IBindingService.class);
        commandImageService = (ICommandImageService) workbench
                .getService(ICommandImageService.class);
        commandService = (ICommandService) workbench
                .getService(ICommandService.class);
        contextService = (IContextService) workbench
                .getService(IContextService.class);
    }

    /**
     * Updates the interface as the key sequence has changed. This finds the
     * selected item. If the selected item is a binding, then it updates the
     * binding -- either by updating a user binding, or doing the deletion
     * marker dance with a system binding. If the selected item is a
     * parameterized command, then a binding is created based on the data
     * controls.
     */
    private final void keySequenceChanged() {
        long startTime = 0L;
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }

        final KeySequence keySequence = keySequenceText.getKeySequence();
        if (!keySequence.isComplete()) {
            return;
        }

        if ((keySequence == null) || (keySequence.isEmpty())) {
            ISelection selection = filteredTree.getViewer().getSelection();
            if (selection instanceof IStructuredSelection) {
                IStructuredSelection structuredSelection = (IStructuredSelection) selection;
                final Object node = structuredSelection.getFirstElement();
                if (node instanceof KeyBinding) {
                    bindingRemove((KeyBinding) node);
                }
            }
            return;
        }

        ISelection selection = filteredTree.getViewer().getSelection();
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection structuredSelection = (IStructuredSelection) selection;
            final Object node = structuredSelection.getFirstElement();
            if (node != null) {
                final Object object = node;
                selection = whenCombo.getSelection();
                final String contextId;
                if (selection instanceof IStructuredSelection) {
                    structuredSelection = (IStructuredSelection) selection;
                    final Object firstElement = structuredSelection
                            .getFirstElement();
                    if (firstElement == null) {
                        contextId = IContextIds.CONTEXT_ID_WINDOW;
                    } else {
                        contextId = ((Context) firstElement).getId();
                    }
                } else {
                    contextId = IContextIds.CONTEXT_ID_WINDOW;
                }
                if (object instanceof KeyBinding) {
                    KeyBinding keyBinding = (KeyBinding) object;
                    if (!keyBinding.getContextId().equals(contextId)
                            || !keyBinding.getKeySequence().equals(keySequence)) {
                        final KeyBinding binding = new KeyBinding(
                                keySequence,
                                keyBinding.getParameterizedCommand(),
                                getSchemeId(),
                                contextId, null, null, null, Binding.USER);

                        ArrayList extraSystemDeletes = new ArrayList ();
                        if (keyBinding.getType() == Binding.USER) {
                            localChangeManager.removeBinding(keyBinding);
                        } else {
                            // TODO This should be the user's personal scheme.
 Collection previousConflictMatches = (Collection ) localChangeManager
                                    .getActiveBindingsDisregardingContext().get(
                                            keyBinding.getTriggerSequence());
                            KeyBinding deleteBinding = new KeyBinding(
                                    keyBinding.getKeySequence(), null,
                                    keyBinding.getSchemeId(), keyBinding
                                            .getContextId(), null, null, null,
                                    Binding.USER);
                            localChangeManager.addBinding(deleteBinding);
                            if (previousConflictMatches != null) {
                                Iterator i = previousConflictMatches.iterator();
                                while (i.hasNext()) {
                                    Binding b = (Binding) i.next();
                                    if (b != keyBinding && deletes(deleteBinding, b)) {
                                        extraSystemDeletes.add(b);
                                    }
                                }
                            }
                        }
                        localChangeManager.addBinding(binding);
                        // update the model
 bindingModel.remove(keyBinding);
                        bindingModel.add(binding);
                        if (!extraSystemDeletes.isEmpty()) {
                            Iterator i = extraSystemDeletes.iterator();
                            while (i.hasNext()) {
                                KeyBinding b = (KeyBinding) i.next();
                                KeyBinding newBinding = new KeyBinding(b.getKeySequence(), b
                                        .getParameterizedCommand(), b.getSchemeId(), b
                                        .getContextId(), null, null, null, Binding.USER);
                                localChangeManager.addBinding(newBinding);

                                bindingModel.remove(b);
                                bindingModel.add(newBinding);
                            }
                        }
                        updateConflicts(keyBinding);
                        updateConflicts(binding);
                        // end update the model
 update();
                        filteredTree.getViewer().setSelection(
                                new StructuredSelection(binding), true);
                    }
                } else if (object instanceof ParameterizedCommand) {
                    // TODO This should use the user's personal scheme.
 final KeyBinding binding = new KeyBinding(keySequence,
                            (ParameterizedCommand) object,
                            getSchemeId(),
                            contextId, null, null, null, Binding.USER);
                    localChangeManager.addBinding(binding);
                    // update the model
 // end update the model
 bindingModel.add(binding);
                    commandModel.remove(object);
                    updateConflicts(binding);
                    update();

                    filteredTree.getViewer().setSelection(
                            new StructuredSelection(binding), true);
                }
            }
        }
        if (DEBUG) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Tracing.printTrace(TRACING_COMPONENT, "keySequenceChanged in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }
    }

    /**
     * Logs the given exception, and opens an error dialog saying that something
     * went wrong. The exception is assumed to have something to do with the
     * preference store.
     *
     * @param exception
     * The exception to be logged; must not be <code>null</code>.
     */
    private final void logPreferenceStoreException(final Throwable exception) {
        final String message = NewKeysPreferenceMessages.PreferenceStoreError_Message;
        String exceptionMessage = exception.getMessage();
        if (exceptionMessage == null) {
            exceptionMessage = message;
        }
        final IStatus status = new Status(IStatus.ERROR,
                WorkbenchPlugin.PI_WORKBENCH, 0, exceptionMessage, exception);
        WorkbenchPlugin.log(message, status);
        StatusUtil.handleStatus(message, exception, StatusManager.SHOW);
    }

    protected final void performDefaults() {

        // Ask the user to confirm
 final String title = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxText;
        final String message = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxMessage;
        final boolean confirmed = MessageDialog.openConfirm(getShell(), title,
                message);

        if (confirmed) {
            // Fix the scheme in the local changes.
 final String defaultSchemeId = bindingService.getDefaultSchemeId();
            final Scheme defaultScheme = localChangeManager
                    .getScheme(defaultSchemeId);
            try {
                localChangeManager.setActiveScheme(defaultScheme);
            } catch (final NotDefinedException e) {
                // At least we tried....
 }

            // Fix the bindings in the local changes.
 final Binding[] currentBindings = localChangeManager.getBindings();
            final int currentBindingsLength = currentBindings.length;
            final Set trimmedBindings = new HashSet ();
            for (int i = 0; i < currentBindingsLength; i++) {
                final Binding binding = currentBindings[i];
                if (binding.getType() != Binding.USER) {
                    trimmedBindings.add(binding);
                }
            }
            final Binding[] trimmedBindingArray = (Binding[]) trimmedBindings
                    .toArray(new Binding[trimmedBindings.size()]);
            localChangeManager.setBindings(trimmedBindingArray);

            // Apply the changes.
 try {
                bindingService.savePreferences(defaultScheme,
                        trimmedBindingArray);
            } catch (final IOException e) {
                logPreferenceStoreException(e);
            }
            long startTime = 0L;
            if (DEBUG) {
                startTime = System.currentTimeMillis();
            }
            busyRefillTree();
            if (DEBUG) {
                final long elapsedTime = System.currentTimeMillis() - startTime;
                Tracing.printTrace(TRACING_COMPONENT,
                        "performDefaults:model in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }
        }

        setScheme(localChangeManager.getActiveScheme());

        super.performDefaults();
    }

    /**
     * We're re-filling the entire tree, both bindings and commands. It's
     * loud.
     */
    private void busyRefillTree() {
        if (bindingModel==null) {
            // we haven't really been created yet.
 return;
        }
        BusyIndicator.showWhile(filteredTree.getViewer().getTree()
                .getDisplay(), new Runnable () {
            public void run() {
                try {
                    filteredTree.getViewer().getTree().setRedraw(false);

                    bindingModel.clear();
                    commandModel.clear();
                    Collection comeBack = localChangeManager
                            .getActiveBindingsDisregardingContextFlat();
                    bindingModel.addAll(comeBack);

                    // showAllCheckBox.setSelection(false);
 fillInCommands();
                } finally {
                    filteredTree.getViewer().getTree().setRedraw(true);
                }
            }
        });
        updateDataControls();
    }

    public final boolean performOk() {
        // Save the preferences.
 try {
            bindingService.savePreferences(
                    localChangeManager.getActiveScheme(), localChangeManager
                            .getBindings());
        } catch (final IOException e) {
            logPreferenceStoreException(e);
        }
        saveState(getDialogSettings());
        return super.performOk();
    }

    /**
     * Handles the selection event on the add binding button. This adds a new
     * binding based on the current selection.
     *
     * @param event
     * Ignored.
     */
    private final void selectAddBindingButton(final SelectionEvent event) {
        long startTime = 0L;
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }

        // Check to make sure we've got a selection.
 final TreeViewer viewer = filteredTree.getViewer();
        final ISelection selection = viewer.getSelection();
        if (!(selection instanceof IStructuredSelection)) {
            return;
        }

        final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
        final Object firstElement = structuredSelection.getFirstElement();
        final Object value = firstElement;
        if (value instanceof KeyBinding) {
            bindingAdd((KeyBinding) value);
        } else if (value instanceof ParameterizedCommand) {
            bindingText.setFocus();
        }

        if (DEBUG) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Tracing.printTrace(TRACING_COMPONENT, "selectAddBindingButton in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }
    }

    /**
     * Handles the selection event on the remove binding button. This removes
     * the selected binding.
     *
     * @param event
     * Ignored.
     */
    private final void selectRemoveBindingButton(final SelectionEvent event) {
        long startTime = 0L;
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }
        // Check to make sure we've got a selection.
 final TreeViewer viewer = filteredTree.getViewer();
        final ISelection selection = viewer.getSelection();
        if (!(selection instanceof IStructuredSelection)) {
            return;
        }

        final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
        final Object firstElement = structuredSelection.getFirstElement();
        final Object value = firstElement;
        if (value instanceof KeyBinding) {
            bindingRemove((KeyBinding) value);
        } else if (value == markedParameterizedCommand) {
            commandModel.remove(markedParameterizedCommand);
            markedParameterizedCommand = null;
            markedContextId = null;
            update();
        }
        if (DEBUG) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Tracing.printTrace(TRACING_COMPONENT,
                    "selectRemoveBindingButton in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }
    }

    private final void selectRestoreBindingButton(final SelectionEvent event) {
        long startTime = 0L;
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }
        // Check to make sure we've got a selection.
 final TreeViewer viewer = filteredTree.getViewer();
        final ISelection selection = viewer.getSelection();
        if (!(selection instanceof IStructuredSelection)) {
            return;
        }

        final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
        final Object firstElement = structuredSelection.getFirstElement();
        final Object value = firstElement;
        if (value instanceof KeyBinding) {
            bindingRestore((KeyBinding) value);
        } else if (value instanceof ParameterizedCommand) {
            bindingRestore((ParameterizedCommand) value, true);
        }
        if (DEBUG) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Tracing.printTrace(TRACING_COMPONENT,
                    "selectRestoreBindingButton in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }
    }

    /**
     * Handles a selection event on the scheme combo. If the scheme has changed,
     * then the local change manager is updated, and the page's contents are
     * updated as well.
     *
     * @param event
     * The selection event; must not be <code>null</code>.
     */
    private final void selectSchemeCombo(final SelectionChangedEvent event) {
        final ISelection selection = event.getSelection();
        if (selection instanceof IStructuredSelection) {
            final Object firstElement = ((IStructuredSelection) selection)
                    .getFirstElement();
            if (firstElement instanceof Scheme) {
                final Scheme newScheme = (Scheme) firstElement;
                if (newScheme != localChangeManager.getActiveScheme()) {
                    try {
                        localChangeManager.setActiveScheme(newScheme);
                        busyRefillTree();
                    } catch (final NotDefinedException e) {
                        // TODO The scheme wasn't valid.
 }
                }
            }
        }
    }

    /**
     * If the row has changed, then update the data controls.
     */
    private final void selectTreeRow(final SelectionChangedEvent event) {
        updateDataControls();
    }

    /**
     * Sets the currently selected scheme. Setting the scheme always triggers an
     * update of the underlying widgets.
     *
     * @param scheme
     * The scheme to select; may be <code>null</code>.
     */
    private final void setScheme(final Scheme scheme) {
        schemeCombo.setSelection(new StructuredSelection(scheme));
    }

    /**
     * Updates all of the controls on this preference page in response to a user
     * interaction.
     */
    private final void update() {
        updateTree();
        updateDataControls();
    }

    /**
     * Updates the data controls to match the current selection, if any.
     */
    private final void updateDataControls() {
        final ISelection selection = filteredTree.getViewer().getSelection();
        if (selection instanceof IStructuredSelection) {
            final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
            final Object node = structuredSelection.getFirstElement();
            if (node != null) {
                final Object object = node;
                if (object instanceof KeyBinding) {
                    final KeyBinding binding = (KeyBinding) object;
                    try {
                        commandNameValueLabel.setText(binding
                                .getParameterizedCommand().getName());
                        String description = binding.getParameterizedCommand()
                                .getCommand().getDescription();
                        if (description == null) {
                            description = Util.ZERO_LENGTH_STRING;
                        }
                        descriptionValueText.setText(description);
                    } catch (final NotDefinedException e) {
                        // It's probably okay to just let this one slide.
 }
                    whenCombo.setSelection(new StructuredSelection(
                            contextService.getContext(binding.getContextId())));
                    keySequenceText.setKeySequence(binding.getKeySequence());

                } else if (object instanceof ParameterizedCommand) {
                    final ParameterizedCommand command = (ParameterizedCommand) object;
                    try {
                        commandNameValueLabel.setText(command.getName());
                        String description = command.getCommand()
                                .getDescription();
                        if (description == null) {
                            description = Util.ZERO_LENGTH_STRING;
                        }
                        descriptionValueText.setText(description);
                    } catch (final NotDefinedException e) {
                        // It's probably okay to just let this one slide.
 }
                    keySequenceText.clear();
                    if (command == markedParameterizedCommand) {
                        whenCombo.setSelection(new StructuredSelection(
                                contextService.getContext(markedContextId)));
                    } else {
                        whenCombo
                                .setSelection(new StructuredSelection(
                                        contextService
                                                .getContext(IContextIds.CONTEXT_ID_WINDOW)));
                    }
                }
            } else {
                commandNameValueLabel.setText(""); //$NON-NLS-1$
 descriptionValueText.setText(""); //$NON-NLS-1$
 keySequenceText.clear();
                whenCombo.setSelection(null);
            }
        }
    }

    private final void updateTree() {
        long startTime = 0L;
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }

        final TreeViewer viewer = filteredTree.getViewer();

        // Add the marked parameterized command, if any.
 if (markedParameterizedCommand != null) {
            commandModel.add(markedParameterizedCommand);
            markedParameterizedCommand = null;
        }

        // Repack all of the columns.
 final Tree tree = viewer.getTree();
        final TreeColumn[] columns = tree.getColumns();

        columns[BindingLabelProvider.COLUMN_COMMAND].setWidth(240);
        columns[BindingLabelProvider.COLUMN_TRIGGER_SEQUENCE].setWidth(130);
        columns[BindingLabelProvider.COLUMN_WHEN].setWidth(130);
        columns[BindingLabelProvider.COLUMN_CATEGORY].setWidth(130);
        columns[BindingLabelProvider.COLUMN_USER].setWidth(50);

        if (DEBUG) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
            Tracing.printTrace(TRACING_COMPONENT, "Refreshed page in " //$NON-NLS-1$
 + elapsedTime + "ms"); //$NON-NLS-1$
 }
    }

    /**
     * Save the state of the receiver.
     *
     * @param dialogSettings
     */
    public void saveState(IDialogSettings dialogSettings) {
        if (dialogSettings == null) {
            return;
        }
        dialogSettings.put(TAG_FIELD, showAllCheckBox.getSelection());
        dialogSettings.put(TAG_FILTER_ACTION_SETS, filterActionSetContexts);
        dialogSettings.put(TAG_FILTER_INTERNAL, filterInternalContexts);
        dialogSettings.put(TAG_FILTER_UNCAT, filteredTree.isFilteringCategories());
    }

    protected IDialogSettings getDialogSettings() {
        IDialogSettings workbenchSettings = WorkbenchPlugin.getDefault()
                .getDialogSettings();

        IDialogSettings settings = workbenchSettings
                .getSection(TAG_DIALOG_SECTION);

        if (settings == null) {
            settings = workbenchSettings.addNewSection(TAG_DIALOG_SECTION);
        }
        return settings;
    }

    protected Object [] getContexts() {

        Context[] contexts = contextService.getDefinedContexts();
        List filteredContexts = new ArrayList ();
        try {
            if (filterActionSetContexts) {
                for (int i = 0; i < contexts.length; i++) {
                    String parentId = contexts[i].getParentId();
                    boolean check = false;
                    if (contexts[i].getId().equalsIgnoreCase(
                            CONTEXT_ID_ACTION_SETS)) {
                        check = true;
                    }
                    while (parentId != null) {
                        if (parentId.equalsIgnoreCase(CONTEXT_ID_ACTION_SETS)) {
                            check = true;
                        }
                        parentId = contextService.getContext(parentId)
                                .getParentId();
                    }
                    if (!check) {
                        filteredContexts.add(contexts[i]);
                    }
                }
            } else {
                filteredContexts.addAll(Arrays.asList(contexts));
            }

            if (filterInternalContexts) {
                for (int i = 0; i < filteredContexts.size(); i++) {
                    if (((Context) filteredContexts.get(i)).getId().indexOf(
                            CONTEXT_ID_INTERNAL) != -1) {
                        filteredContexts.remove(i);
                    }
                }
            }

        } catch (NotDefinedException e) {
            return contexts;
        }

        return filteredContexts.toArray();
    }

    private void updateWhenCombo() {
        ISelection selection = filteredTree.getViewer().getSelection();
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection structuredSelection = (IStructuredSelection) selection;
            Object node = structuredSelection.getFirstElement();
            if (node != null) {
                final Object object = node;
                selection = whenCombo.getSelection();
                final String contextId;
                if (selection instanceof IStructuredSelection) {
                    structuredSelection = (IStructuredSelection) selection;
                    final Object firstElement = structuredSelection
                            .getFirstElement();
                    if (firstElement == null) {
                        contextId = IContextIds.CONTEXT_ID_WINDOW;
                    } else {
                        contextId = ((Context) firstElement).getId();
                    }
                } else {
                    contextId = IContextIds.CONTEXT_ID_WINDOW;
                }
                if (object instanceof KeyBinding) {
                    KeyBinding keyBinding = (KeyBinding) object;
                    if (!keyBinding.getContextId().equals(contextId)) {
                        final KeyBinding binding = new KeyBinding(
                                keyBinding.getKeySequence(),
                                keyBinding.getParameterizedCommand(),
                                getSchemeId(),
                                contextId, null, null, null, Binding.USER);

                        if (keyBinding.getType() == Binding.USER) {
                            localChangeManager.removeBinding(keyBinding);
                        } else {
                            localChangeManager.addBinding(new KeyBinding(
                                    keyBinding.getKeySequence(), null,
                                    keyBinding.getSchemeId(), keyBinding
                                            .getContextId(), null, null, null,
                                    Binding.USER));
                        }
                        localChangeManager.addBinding(binding);
                        // update the model
 bindingModel.remove(keyBinding);
                        bindingModel.add(binding);
                        updateConflicts(keyBinding);
                        updateConflicts(binding);
                        // end update the model
 update();
                        filteredTree.getViewer().setSelection(
                                new StructuredSelection(binding), true);
                    }
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.preference.PreferencePage#applyData(java.lang.Object)
     */
    public void applyData(Object data) {
        if (data instanceof Binding && filteredTree != null) {
            filteredTree.getViewer().setSelection(
                    new StructuredSelection(data), true);
        }
    }
    
    public String getSchemeId() {
        ISelection sel = schemeCombo.getSelection();
        if (sel instanceof IStructuredSelection) {
            Object o = ((IStructuredSelection)sel).getFirstElement();
            if (o instanceof Scheme) {
                return ((Scheme)o).getId();
            }
        }
        return IBindingService.DEFAULT_DEFAULT_ACTIVE_SCHEME_ID;
    }
}

